#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "load.h"
#include "endian.h"

Elf32_Ehdr load_header(FILE * f) {
    Elf32_Ehdr header;
    int i;
    fseek (f ,0 , SEEK_SET);
    for (i = 0; i < 16; i++) {
        fread (&header.e_ident[i],1,1,f);
    }
    fread (&header.e_type,1,2,f);
    fread (&header.e_machine,1,2,f);
    fread (&header.e_version,1,4,f);
    fread (&header.e_entry,1,4,f);
    fread (&header.e_phoff,1,4,f);
    fread (&header.e_shoff,1,4,f);
    fread (&header.e_flags,1,4,f);
    fread (&header.e_ehsize,1,2,f);
    fread (&header.e_phentsize,1,2,f);
    fread (&header.e_phnum,1,2,f);
    fread (&header.e_shentsize,1,2,f);
    fread (&header.e_shnum,1,2,f);
    fread (&header.e_shstrndx,1,2,f);
    
    header.e_type = be16toh(header.e_type);
    header.e_machine = be16toh(header.e_machine);
    header.e_version = be32toh(header.e_version);
    header.e_entry = be32toh(header.e_entry);
    header.e_phoff = be32toh(header.e_phoff);
    header.e_shoff = be32toh(header.e_shoff);
    header.e_flags = be32toh(header.e_flags);
    header.e_ehsize = be16toh(header.e_ehsize);
    header.e_phentsize = be16toh(header.e_phentsize);
    header.e_phnum = be16toh(header.e_phnum);
    header.e_shentsize = be16toh(header.e_shentsize);
    header.e_shnum = be16toh(header.e_shnum);
    header.e_shstrndx = be16toh(header.e_shstrndx);

    return header;
}


Elf32_Shdr* load_section_headers(FILE * f) {
    Elf32_Ehdr header = load_header(f);
    int i;
    Elf32_Shdr* sheader = NULL;
    sheader = malloc(sizeof(Elf32_Shdr) * header.e_shnum);
    if (sheader != NULL) {
        fseek (f ,header.e_shoff , SEEK_SET);
        for (i = 0; i < header.e_shnum; i++) {
            fread (&sheader[i].sh_name,1,4,f);
            fread (&sheader[i].sh_type,1,4,f);
            fread (&sheader[i].sh_flags,1,4,f);
            fread (&sheader[i].sh_addr,1,4,f);
            fread (&sheader[i].sh_offset,1,4,f);
            fread (&sheader[i].sh_size,1,4,f);
            fread (&sheader[i].sh_link,1,4,f);
            fread (&sheader[i].sh_info,1,4,f);
            fread (&sheader[i].sh_addralign,1,4,f);
            fread (&sheader[i].sh_entsize,1,4,f);
            
                    sheader[i].sh_name = be32toh(sheader[i].sh_name);
                        sheader[i].sh_type = be32toh(sheader[i].sh_type);
                        sheader[i].sh_flags = be32toh(sheader[i].sh_flags);
                        sheader[i].sh_addr = be32toh(sheader[i].sh_addr);
                        sheader[i].sh_offset = be32toh(sheader[i].sh_offset);
                        sheader[i].sh_size = be32toh(sheader[i].sh_size);
                        sheader[i].sh_link = be32toh(sheader[i].sh_link);
                        sheader[i].sh_info = be32toh(sheader[i].sh_info);
                        sheader[i].sh_addralign = be32toh(sheader[i].sh_addralign);
                        sheader[i].sh_entsize = be32toh(sheader[i].sh_);
        }
    }
    return sheader;
}

Elf32_Sym* load_symbol_table(FILE * f) {
    int i;
    Elf32_Off        offset;                /* Section file offset */
    Elf32_Word size;
    Elf32_Ehdr header = load_header(f);
    Elf32_Shdr* sheader = load_section_headers(f);

    for(i=0;i<header.e_shnum;i++){
        if(sheader[i].sh_type==2){
            offset=sheader[i].sh_offset;
            size=sheader[i].sh_size;        
        }
    }


    Elf32_Sym* entry_stable = NULL;
    entry_stable = malloc(sizeof(Elf32_Sym)*size);

    if (entry_stable != NULL) {
        fseek (f ,offset , SEEK_SET);
        for (i = 0; i < size/16; i++) {
            fread (&entry_stable[i].st_name,1,4,f);
            fread (&entry_stable[i].st_value,1,4,f);
            fread (&entry_stable[i].st_size,1,4,f);
            fread (&entry_stable[i].st_info,1,1,f);
            fread (&entry_stable[i].st_other,1,1,f);
            fread (&entry_stable[i].st_shndx,1,2,f);
        }
    }
    return entry_stable;
}


Elf32_Rel* load_relocation_table(FILE * f, char*** noms, int** nbrels, int * nb) {
    Elf32_Ehdr header = load_header(f);
    Elf32_Shdr* sheader = load_section_headers(f);
    int* nbr = NULL;
    int *infos = NULL;
    int i, j = 0, y=0, total = 0; 
    *nb = 0;
    char** tab = NULL;
    char* name;
    Elf32_Rel* reltable = NULL;
    for (i = 0; i < header.e_shnum; i++) {
        if (sheader[i].sh_type == 9) {
            *nb = *nb + 1;
        }
    }
    infos = malloc(sizeof(int)*(*nb)*3);
    tab = malloc(sizeof(char*)*(*nb));
    nbr = malloc(sizeof(int)*(*nb));
    if (infos == NULL  || tab == NULL || nbr == NULL) {
        free(sheader);
        free(infos);
        free(tab);
        free(nbr);
        return NULL;
    }
    for (i = 0; i < header.e_shnum; i++) {
        if (sheader[i].sh_type == 9) {
            infos[j] = i;
            infos[j+1] = sheader[i].sh_size;
            infos[j+2] = sheader[i].sh_offset;
            j = j + 3;
        }
    }
    for (i = 0; i < (*nb); i++) { //Pour chaque REL section
        name = getnom(f, sheader[infos[i*3]].sh_name);
        fseek(f, infos[i + 2], SEEK_SET);
        tab[i] = name;
        nbr[i] =  infos[i * 3 + 1] / 8;
    }
    for (j=0; j<(*nb);j++) {
        total +=     nbr[j];
    }
    reltable = malloc(sizeof(Elf32_Rel)*total);

    for (j = 0; j < nbr[i]; j++) {
        
        fread(&reltable[y].r_offset, 1, 4, f);
        fread(&reltable[y].r_info, 1, 4, f);
        y++;
    }

    *noms = tab;
    *nbrels = nbr;
    free(sheader);
    free(infos);
        
    return reltable;
}


int estELF(char * str) {
    return ((char)str[strlen(str)-2] == '.' 
        && (char)str[strlen(str)-1] == 'o'); 
}

char* getnom(FILE * f, long unsigned int name) {
    Elf32_Ehdr header = load_header(f);
    Elf32_Shdr* sheader = load_section_headers(f);
    int deb =0, size = 1, j = 0;
    char c;
    char* str;
    fseek (f ,header.e_shoff + (header.e_shentsize * header.e_shstrndx) +16, SEEK_SET);
    fread(&deb, 1, 4, f);
    fseek (f ,deb + name , SEEK_SET);
    c = (char)fgetc(f);
    while (c != '\0') {
        size++;
        c = (char)fgetc(f);
    }
    str = malloc(sizeof(char)*size);
    if (str == NULL ) {
        return NULL;
    }
    fseek (f ,deb + name , SEEK_SET);
    c = (char)fgetc(f);
    while (c != '\0') {
        str[j] = c;
        c = (char)fgetc(f);
        j++;
    }
    str[j] = c;
    free(sheader);
    return str;
}
